﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Facebook;
using Db = FeedBack.Model;

namespace FeedBack {
    public class SqlImporter {
        private FacebookClient fb;

        public SqlImporter(FacebookClient fb) {
            this.fb = fb;
        }

        public void Import(IEnumerable<JsonObject> stories) {
            Db.FeedBackEntities entities = new Db.FeedBackEntities();

            DateTime lastCall = DateTime.MinValue;
            int batchSize = 50;
            List<JsonObject> candidates = new List<JsonObject>();
            Dictionary<string, JsonObject> candidateStories = new Dictionary<string, JsonObject>();

            foreach (var candidateStory in stories) {
                if (Configuration.SavePagePostOnly && candidateStory.Get<JsonObject>("to") != null) {
                    continue;
                }

                var id = candidateStory.Get<string>("id");
                Db.Story entity = entities.Stories.SingleOrDefault(o => o.StoryId == id);

                if (entity == null || Configuration.Overwrite) {
                        candidateStories[candidateStory.Get<string>("id")] = candidateStory;
                }

                if (candidateStories.Count >= batchSize) {
                    lastCall = HandleBatch(entities, lastCall, candidateStories);
                    candidateStories.Clear();
                }
            }

            HandleBatch(entities, lastCall, candidateStories);
            entities.Dispose();
        }

        private DateTime HandleBatch(Db.FeedBackEntities entities, DateTime lastCall, Dictionary<string, JsonObject> candidateStories) {
            var ids = candidateStories.Keys;
            var candidateEntities = entities.Stories.Where(o => ids.Contains(o.StoryId)).ToDictionary(o => o.StoryId);
            var query = new List<FacebookBatchParameter>();

            foreach (var id in ids) {

                Db.Story entity = candidateEntities.ContainsKey(id) ? candidateEntities[id] : null;

                if (entity == null || Configuration.Overwrite) {

                    var criterion = new FacebookBatchParameter(string.Format("{0}?fields=comments.summary(true),likes.summary(true),shares", id));
                    query.Add(criterion);
                }
            }

            Dictionary<string, JsonObject> results = new Dictionary<string, JsonObject>();
            if (query.Any()) {
                for (int i = 0; i < 10; i++) {
                    try {
                        Debug.WriteLine(string.Format("Making request for {0} items", query.Count));
                        var response = (fb.Batch(query.ToArray()) as IList<object>).Cast<JsonObject>();
                        if (response.Where(o => o.ContainsKey("error") && o.Get("error").Get<long>("code") == 613).Count() == query.Count) {
                            for (int j = 0; j < 60; j++) {
                                Thread.Sleep(1000);
                                Debug.WriteLine(string.Format("{1} Retry in {0} seconds", 60 + i * 10 - j, i));
                            }
                            fb.AccessToken = Configuration.Token;
                            continue;
                        }
                        if (response.Where(o => o.ContainsKey("error")).Count() == query.Count) {
                            break;
                        }
                        results = response.Where(o => !(o is Exception))
                                        .Cast<JsonObject>()
                                        .Where(o => !string.IsNullOrWhiteSpace(o.Get<string>("id")))
                                        .ToDictionary(o => o.Get<string>("id"));
                        break;
                    }
                    catch (Exception e) {
                        if (e.Message.Contains("Error validating access token")) {
                            throw;
                        }
                        else {
                            System.Diagnostics.Debug.WriteLine(e.Message);
                        }
                    }
                    finally {
                        lastCall = DateTime.Now;
                    }
                }
            }

            foreach (var story in candidateStories.Values) {
                string id = story.Get<string>("id");
                System.Diagnostics.Debug.WriteLine(id);

                JsonObject json = results.ContainsKey(id) ? results[id] : null;
                if (json != null) {
                    story["like_count"] = (int)json.Get("likes").Get("summary").Get<long>("total_count");
                    story["comment_count"] = (int)json.Get("comments").Get("summary").Get<long>("total_count");
                    if (json.Get("shares") != null) {
                        story["shares"] = json.Get("shares");
                    }
                }

                Db.Story entity = candidateEntities.ContainsKey(id) ? candidateEntities[id] : null;
                if (entity == null) {
                    Debug.WriteLine(string.Format("Inserting Likes: {0} Comments: {1} Date: {2}", story.Get<int>("like_count"), story.Get<int>("comment_count"), story.Get<string>("updated_time")));
                    entity = new Db.Story(story);
                    entities.Stories.Add(entity);
                }
                else if ((story.ContainsKey("like_count") && entity.LikeCount != (int)story["like_count"]) ||
                         (story.ContainsKey("comment_count") && entity.CommentCount != (int)story["comment_count"]) ||
                         DateTime.Parse(story["updated_time"] as string) > entity.UpdatedTime) {
                             Debug.WriteLine(string.Format("Updating Likes: {0} Comments: {1} Date: {2}", story.Get<int>("like_count"), story.Get<int>("comment_count"), entity.UpdatedTime));
                    entity.Copy(story);
                }
            }
            int affected = entities.SaveChanges();

            return lastCall;
        }

    }
}
